package cz.semjobKB.search.service.impl;

import cz.semjobKB.conllx.api.IDocument;
import cz.semjobKB.conllx.api.ISentence;
import cz.semjobKB.conllx.api.IWord;
import cz.semjobKB.conllx.api.impl.Word;
import cz.semjobKB.conllx.node.EConstituent;
import cz.semjobKB.conllx.node.EWordType;
import cz.semjobKB.knowledge.api.IExtractedKnowledge;
import cz.semjobKB.knowledge.impl.GenerateKnowledgeResult;
import cz.semjobKB.search.api.*;
import cz.semjobKB.search.impl.Match;
import cz.semjobKB.search.impl.SearchMatch;
import cz.semjobKB.search.service.api.ISearchService;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;

/**
 * Search service used to extract information from the document based on the search configuration
 */
@Service
public class SearchService implements ISearchService{

	@Autowired
	private GenerateKnowledgeResult generateKnowledgeResult;

    /*
        Limits the recursive search in the sentence parsed trees
     */
	public int RECURSIVE_LIMIT = 2;
	public List<ISentence> frazesList = new ArrayList<ISentence>();
	public List<ISentence> sentList = new ArrayList<ISentence>();	
		
	public IExtractedKnowledge extractBase(IDocument document, ISearchRules searchRules)
			throws RuleCorrectnessException {
		List<SearchMatch> searchResult = new ArrayList<SearchMatch>();
		List<ISentence> sentences = document.getSentences();						
		frazesList.clear();
		sentList.clear();


		int sentenceNumber = 0;				
		for (ISentence sentence : sentences) {
			sentenceNumber++;			
			SearchMatch sentenceResult = findMatch(sentence, sentenceNumber, searchRules);			

			if (sentenceResult != null) {
				searchResult.add(sentenceResult);
			}

		}
		IExtractedKnowledge extractedKnowledge = generateKnowledgeResult.generateResult(searchResult, sentences);

		return extractedKnowledge;
	}

    /*
        Removes the possible duplicate results of one rule set. Duplicity may occur if one rule is a sub-part of another
     */
	private void removeVassalDuplicateMatches(List<IMatch> matchList, IRulesSet rulesSet) {
		List<IMatch> toRemoveList = new ArrayList<IMatch>();
		for (IMatch match : matchList) {
			if (match.isVassal()) {				
				for ( IMatch others : matchList) {
					boolean hasPositionRootMatch = match.getPositionRootMatch() == others.getPositionRootMatch(); 
					boolean isDifferentMatch = match != others;
					boolean isNotVassalToo = !others.isVassal();		
					if ( hasPositionRootMatch && isDifferentMatch && isNotVassalToo) {
						toRemoveList.add(match);
						continue;
					}
				}				
			}
		}
		for (IMatch toRemove : toRemoveList) {
			matchList.remove(toRemove);
		}
	}

    /*
        Tries to find a match for the given sentence and rule set. Only for triple search
     */
	private List<IMatch> findMatches (ISentence sentence, int sentPossition, IRulesSet ruleSet) {
		List<Word> words = sentence.getWords();

		//for each root word, try to match the nodes rule on the sentence	
		List<IMatch> globalMatch = new ArrayList<IMatch>();
		for (IRule rule : ruleSet.getRules()) {	
			//Search for parent node
			EConstituent rootType = rule.getNodes().get(0).getType();
			List<Word> rootWords = new ArrayList<Word>();  
			List<Integer> rootNodePos = new ArrayList<Integer>();

			for (int pos = 0; pos < words.size(); pos++) {
				if (words.get(pos).getConstituent() == rootType) {
					rootWords.add(words.get(pos));
					rootNodePos.add(pos);
					if (rule.getName().equals("PREDICATE")) {
						globalMatch.add(new Match(words.get(pos), rule.getName(), rule.isVassal(), pos, sentPossition));						
					}
				}
			}

			if (rootWords.size() == 0) {
				break;
			}
			if (rule.getName().equals("PREDICATE")) {
				break;
			}

			for (int pos = 0; pos < rootWords.size(); pos++) {

				//every node in a rule
				for (int nodePos = 1; nodePos < rule.getNodes().size(); nodePos++) {
					//loop through the sentence
					for (Word word : words) {

						if(word.getParentNode() == 0) {
							continue;
						}

						if (rule.getNodes().get(nodePos).getWordType() == null && resursiveMatch(words, rootWords.get(pos), word, nodePos, rule)) {
							globalMatch.add(new Match(word, rule.getName(), rule.isVassal(), rootNodePos.get(pos), sentPossition));
						} else {
							if (rule.getNodes().get(nodePos).getWordType() == word.getWordType() 
									&& resursiveMatch(words, rootWords.get(pos), word, nodePos, rule)) {
								globalMatch.add(new Match(word, rule.getName(), rule.isVassal(), rootNodePos.get(pos), sentPossition));
							}
						}
					}


				}		
			}
		}

		return globalMatch;


	}

    /*
        For given sentence and terminology rule set it tries to find a match
     */
	private List<List<IWord>> findMatchesTerminology (ISentence sentence, IRulesSet ruleSet) {
		List<IWord> words = sentence.getWords();

		//for each root word, try to match the nodes rule on the sentence	
		List<List<IWord>> globalMatch = new ArrayList<List<IWord>>();
		for (IRule rule : ruleSet.getRules()) {	
			//Search for parent node
			EConstituent rootType = rule.getNodes().get(0).getType();
			EWordType rootWordType = null;
			if (rule.getNodes().get(0).getWordType() != null) {
				rootWordType = rule.getNodes().get(0).getWordType();
			}
			List<IWord> rootWords = new ArrayList<IWord>();  
			List<Integer> rootNodePos = new ArrayList<Integer>();

			for (int pos = 0; pos < words.size(); pos++) {
				if (words.get(pos).getConstituent() == rootType) {
					if (rootWordType == null) {
						rootWords.add(words.get(pos));
						rootNodePos.add(pos);
					} else if (rootWordType == words.get(pos).getWordType()) {
						rootWords.add(words.get(pos));
						rootNodePos.add(pos);
					}
				}
			}

			if (rootWords.size() == 0) {
				continue;
			}			

			for (int pos = 0; pos < rootWords.size(); pos++) {
				globalMatch.add(new ArrayList<IWord>());
				globalMatch.get(globalMatch.size() - 1).add(rootWords.get(pos));
				//every node in a rule
				for (int nodePos = 1; nodePos < rule.getNodes().size(); nodePos++) {
					//loop through the sentence
					for (IWord word : words) {

						if(word.getParentNode() == 0) {
							continue;
						}

						if ((word.getParentNode() == sentence.getWords().indexOf(rootWords.get(pos)) + 1) 
								&& (word.getConstituent() == rule.getNodes().get(nodePos).getType())) {
							if (rule.getNodes().get(nodePos).getWordType() == null) {
								globalMatch.get(globalMatch.size() - 1).add(word);
							} else if (word.getWordType() == rule.getNodes().get(nodePos).getWordType()) {
								globalMatch.get(globalMatch.size() - 1).add(word);
							}

						}

					}


				}		
			}
		}

		globalMatch = sortWords(globalMatch, sentence.getWords());

		return globalMatch;


	}

    /*
        Tries to find dependent matches for a given sentence and matched parent. Example predicate and dependent aux verb
     */
	private boolean resursiveMatch(List<Word> sentence, Word parent, Word word, int nodePos, IRule rule) {

		if ( (word.getConstituent() != rule.getNodes().get(nodePos).getType())) {
			return false;
		}
		Word parentNode = word;
		int recursive = RECURSIVE_LIMIT;
		while(recursive > 0) {
			recursive--;
			if (parentNode.getParentNode() == 0) {
				return false;
			}
			if (parentNode.getParentNode() == 0 && parent.getConstituent() != rule.getNodes().get(nodePos).getParentType()) {
				return false;
			}
			if (rule.getName().equals("OBJECT") && sentence.get(parentNode.getParentNode()-1).getConstituent().equals(EConstituent.SUBJECT)) {
				return false;
			}
			if ((parent.getConstituent() == rule.getNodes().get(nodePos).getParentType()) 
					&& (sentence.indexOf(parent) + 1 == parentNode.getParentNode())) {					
				return true;
			}			
			parentNode = sentence.get(parentNode.getParentNode()-1);

		}
		return false;
	}

    /*
        For each possible rule set find a match and replace duplicity
     */
	private SearchMatch findMatch(ISentence sentence, int sentPossition, ISearchRules searchRules) throws RuleCorrectnessException {		
		List<IMatch> pred = new ArrayList<IMatch>();
		if (searchRules.getPredicateRule() != null) {
			pred = findMatches(sentence, sentPossition, searchRules.getPredicateRule());
			removeVassalDuplicateMatches(pred, searchRules.getPredicateRule());
		}

		List<IMatch> sub = new ArrayList<IMatch>();
		if (searchRules.getSubjectRule() != null) {
			sub = findMatches(sentence, sentPossition, searchRules.getSubjectRule());
			removeVassalDuplicateMatches(sub, searchRules.getSubjectRule());
		}

		List<IMatch> obj = new ArrayList<IMatch>();
		if (searchRules.getObjectRule() != null) {
			obj = findMatches(sentence, sentPossition, searchRules.getObjectRule());
			removeVassalDuplicateMatches(obj, searchRules.getObjectRule());
		}

		List<List<IWord>> term = new ArrayList<List<IWord>>();
		if (searchRules.getTerminologyRule() != null) {
			term = findMatchesTerminology(sentence, searchRules.getTerminologyRule());
		}

		return new SearchMatch(pred, sub, obj, term, sentence,  sentPossition);

	}
    /*
        Sorts the words in a match to correspond with the sentence word order
     */
	private List<List<IWord>> sortWords(List<List<IWord>> matchedWords, List<IWord> sentence) {
		List<List<IWord>> sortedWords = new ArrayList<List<IWord>>();
		//for every matched tree
		for (List<IWord> part : matchedWords) {

			List<IWord> sortedPart = new ArrayList<IWord>();			
			//loop through the sentence to get correct order of words
			if (part.size() > 1) {
				for (IWord word : sentence) {

					if (part.contains(word)) {
						sortedPart.add((Word) word);
					}

				}
			} else {
				sortedPart = new ArrayList<IWord>(part);
			}

			sortedWords.add(new ArrayList<IWord>(sortedPart));

		}

		return sortedWords;

	}


}